package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;

/**
 * 聊天室服务端
 */
public class Server {
    private ServerSocket serverSocket;
    //共享所有ClientHandler中的输出流便于广播消息使用
//    private PrintWriter[] allOut = {};
    private Collection<PrintWriter> allOut = new ArrayList<>();

    public Server(){
        try {
            /*
                运行在服务端的ServerSocket主要有两个工作:
                1:打开服务端口,客户端就可以通过这个端口与服务端建立连接
                2:监听服务端口,一旦一个客户端与服务端建立连接就可以立即
                  返回一个Socket,通过它与客户端交互.

                  我们可以把ServerSocket想象为某客服的"总机",所有客户端
                  都可以给总机打电话,而总机下面连接着若干"电话".
             */
            System.out.println("正在启动服务端...");
            /*
                该端口不能与系统其他应用程序已经打开的端口号一致,否则会
                抛出异常:java.net.BindException:address already in use
                遇到该错误需要更换端口号,直到不报错为止.
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            /*
                ServerSocket提供的方法:
                Socket accept()
                此方法是一个阻塞方法,调用后服务端程序"卡主",开始等待
                客户端的连接(相当于等待接电话),一旦一个客户端连接了,
                此时该方法会理解返回一个Socket(相当于接起一个电话)
                通过返回的Socket即可与连接的客户端进行交互.
             */
            while(true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了!");
                //一个客户端连接了就启动一个线程来处理该客户端交互
                ClientHandler clientHandler
                        = new ClientHandler(socket);
                Thread t = new Thread(clientHandler);
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 该线程任务是负责与指定的客户端交互的
     */
    class ClientHandler implements Runnable{
        private Socket socket;
        private String host;//客户端地址信息

        public ClientHandler(Socket socket){
            this.socket = socket;
            //通过socket获取远端计算机地址的字符串格式
            host = socket.getInetAddress().getHostAddress();
        }

        public void run(){
            PrintWriter pw = null;
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr
                        = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                //通过socket获取输出流并通过流连接变成PrintWriter
                pw = new PrintWriter(
                        new BufferedWriter(
                                new OutputStreamWriter(
                                        socket.getOutputStream(),
                                        "UTF-8")
                        ),true
                );
                synchronized (Server.this) {
                    //将输出流存入allOut便于其他ClientHandler访问来转发消息
                    //1对allOut数组扩容
//                    allOut = Arrays.copyOf(allOut, allOut.length + 1);
                    //2将当前输出流存入数组最后一个位置
//                    allOut[allOut.length - 1] = pw;

                    allOut.add(pw);
                }
//                sendMessage(host+"上线了,当前在线人数:"+allOut.length);
                sendMessage(host+"上线了,当前在线人数:"+allOut.size());

                String message;
                /*
                    这里的br.readLine()在读取客户端发送过来的一行字符串
                    时会形成阻塞现象,如果客户端没有发送内容时,该方法会一直
                    "卡主".
                    如果客户端调用了socket.close()与服务端断开连接,那么
                    readLine方法会返回null.
                    如果客户端强行关闭(没有主动调用socket.close()就结束了
                    程序)则readLine方法会抛出异常:
                    java.net.SocketException: Connection reset
                 */
                while ((message = br.readLine()) != null) {
                    System.out.println(host+"说:" + message);
                    //将消息回复给所有客户端
                    sendMessage(host + "说:" + message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //处理客户端断开后的操作

                synchronized (Server.this) {
                    //将对应当前客户端的输出流从allOut中删除
//                    for (int i = 0; i < allOut.length; i++) {
//                        if (allOut[i] == pw) {
//                            allOut[i] = allOut[allOut.length - 1];
//                            allOut = Arrays.copyOf(allOut, allOut.length - 1);
//                            break;
//                        }
//                    }
                    allOut.remove(pw);

                }
//                sendMessage(host+"下线了,当前在线人数:"+allOut.length);
                sendMessage(host+"下线了,当前在线人数:"+allOut.size());


                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

        /**
         * 将消息广播给所有客户端
         * @param message
         */
        private void sendMessage(String message){
            synchronized (Server.this) {
                for (PrintWriter pw : allOut) {
                    pw.println(message);
                }
            }
        }
    }
}









